#include "board_cfg.h"
#include "display_drv.h"
#include "nrf_gpio.h"
#include "nrf_delay.h"
#include "nrf_spi.h"
#include "nrf_spim.h"
#include "nrf_drv_common.h"
#include "app_util_platform.h"
#include "app_trace.h"
#define LOG(fmt, ...) app_trace_log(fmt "\r\n", ##__VA_ARGS__)

#define OLED_SPI_FREQ (SPI_FREQUENCY_FREQUENCY_M8)

#define TIMEOUT_COUNTER_10MS 0x3000UL /*!< timeout for getting rx bytes from slave */
#define SPI_HW_BUFFER_COUNT (2)       //硬件SPI有2个字节缓存

static struct
{
    uint8_t config_mode;             //软件SPI或硬件SPI/spim
    uint8_t current_mode;            //当前正在配置的模式，使用DMA时会使用非DMA来发送指令，所以标记一下方便切换模式
    volatile uint8_t spim_busy_flag; // SPI DMA发送中
    uint8_t spi_data_in_buffer;      //标记多少个字节数据在buffer中，spi有2字节buff缓存
    uint16_t auto_dma_tx_times;      //自动DMA发送的次数

} spi_ctrl;

int get_spi_mode(void)
{
    return spi_ctrl.config_mode;
}
/*********************************************************************************************
 * @brief SPIM的发送完成中断
 * @warning
 ********************************************************************************************/
void SPIM1_IRQHandler(void)
{
    if (nrf_spim_event_check(OLED_SPIXM, NRF_SPIM_EVENT_END))
    {
        if (spi_ctrl.auto_dma_tx_times)
        {
            spi_ctrl.auto_dma_tx_times--;
            nrf_spim_task_trigger(OLED_SPIXM, NRF_SPIM_TASK_START);
        }
        else
        {
            nrf_gpio_pin_set(OLED_CS_PIN);
            spi_ctrl.spim_busy_flag = 0;
        }
        nrf_spim_event_clear(OLED_SPIXM, NRF_SPIM_EVENT_END);
    }
}

/*********************************************************************************************
 * @brief SPI模式切换
 * @warning
 ********************************************************************************************/
void spi_switch_mode(uint8_t config_mode)
{
    //如果有DMA先等待DMA结束
    if (spi_ctrl.current_mode == SPI_CFG_MODE_HARDWARE_DMA)
    {
        uint32_t count = 0;
        while ((spi_ctrl.spim_busy_flag) && (count++ < TIMEOUT_COUNTER_10MS * 30))
        {
            ;
        }
        if (count >= TIMEOUT_COUNTER_10MS * 30) //超时强制处理
        {
            nrf_spim_event_clear(OLED_SPIXM, NRF_SPIM_EVENT_END);
            nrf_spim_disable(OLED_SPIXM);
            OLED_SPIXM->SHORTS = 0;
            spi_ctrl.spim_busy_flag = 0;
        }
    }

    if (spi_ctrl.current_mode != config_mode)
    {
        if (config_mode == SPI_CFG_MODE_HARDWARE_DMA)
        {
            nrf_spim_event_clear(OLED_SPIXM, NRF_SPIM_EVENT_END);
            nrf_spim_enable(OLED_SPIXM);
        }
        else if (config_mode == SPI_CFG_MODE_HARDWARE)
        {
            OLED_SPIX->EVENTS_READY = 0U;
            nrf_spi_enable(OLED_SPIX);
        }
        spi_ctrl.current_mode = config_mode;
    }
}

/*********************************************************************************************
 * @brief
 * @param [in] param1
 * @param [out] param2
 * @return ret
 * @note
 * @warning
 ********************************************************************************************/
void spim_tx(const uint8_t *p_buffer, uint8_t length, uint16_t auto_dma_times)
{
    if (get_spi_mode() != SPI_CFG_MODE_SOFTWARE)
    {
        spi_switch_mode(SPI_CFG_MODE_HARDWARE_DMA);
    }

    OLED_SPIXM->TXD.PTR = (uint32_t)p_buffer;
    OLED_SPIXM->TXD.MAXCNT = length;
    spi_ctrl.auto_dma_tx_times = auto_dma_times;

    spi_ctrl.spim_busy_flag = 1;
    nrf_spim_task_trigger(OLED_SPIXM, NRF_SPIM_TASK_START);
}
/*********************************************************************************************
 * @brief
 * @param [in] param1
 * @warning
 ********************************************************************************************/
void oled_spi_tx_byte(uint8_t data)
{
    if (spi_ctrl.config_mode != SPI_CFG_MODE_SOFTWARE)
    {
        OLED_SPIX->TXD = (uint32_t)(data);
        uint32_t counter = 0;
        /* Wait for the transaction complete or timeout (about 10ms - 20 ms) */
        while ((OLED_SPIX->EVENTS_READY == 0U) && (counter++ < TIMEOUT_COUNTER_10MS))
        {
        }
        /* clear the event to be ready to receive next messages */
        OLED_SPIX->EVENTS_READY = 0U;
        uint8_t dummy = (uint8_t)OLED_SPIX->RXD;
    }
    else
    {
        for (uint8_t i = 0; i < 8; i++)
        {
            nrf_gpio_pin_clear(OLED_CLOCK_PIN);

            if (data & 0x80)
                nrf_gpio_pin_set(OLED_DATA_PIN);
            else
                nrf_gpio_pin_clear(OLED_DATA_PIN);

            data <<= 1;
            nrf_gpio_pin_set(OLED_CLOCK_PIN);
        }
        // nrf_gpio_pin_set(OLED_DATA_PIN);
    }
}

/*********************************************************************************************
 * @brief SPI通信初始化
 * @warning
 ********************************************************************************************/
void oled_spi_init(spi_cfg_mode_t spi_cfg_mode)
{
    uint32_t config_mode;
    static uint32_t spi_rx_buffer;
    // CS pin
    nrf_gpio_cfg_output(OLED_CS_PIN);
    nrf_gpio_pin_set(OLED_CS_PIN);

    OLED_SPIX->ENABLE = (SPI_ENABLE_ENABLE_Disabled << SPI_ENABLE_ENABLE_Pos);

    /* Configure GPIO pins used for pselsck, pselmosi, pselmiso and pselss for SPI1*/
    nrf_gpio_cfg_output(OLED_CLOCK_PIN);
    nrf_gpio_cfg_output(OLED_DATA_PIN);
    nrf_gpio_pin_set(OLED_DATA_PIN);
    nrf_gpio_pin_set(OLED_CLOCK_PIN);

    if (spi_cfg_mode != SPI_CFG_MODE_SOFTWARE)
    {
        // SPIM 参数配置
        OLED_SPIXM->PSEL.SCK = OLED_CLOCK_PIN;
        OLED_SPIXM->PSEL.MOSI = OLED_DATA_PIN;
        OLED_SPIXM->FREQUENCY = (uint32_t)OLED_SPI_FREQ;

#if 0
//#if defined(R30_BOARD)
#warning debug
        OLED_SPIXM->FREQUENCY = (uint32_t)OLED_SPI_FREQ;
#else
        OLED_SPIXM->FREQUENCY = (uint32_t)OLED_SPI_FREQ;
#endif
        config_mode = (SPIM_CONFIG_CPOL_ActiveLow << SPIM_CONFIG_CPOL_Pos) | (SPIM_CONFIG_CPHA_Trailing << SPIM_CONFIG_CPHA_Pos);

        OLED_SPIXM->CONFIG = (config_mode | (SPIM_CONFIG_ORDER_MsbFirst << SPIM_CONFIG_ORDER_Pos));
        OLED_SPIXM->ORC = 0;
        OLED_SPIXM->INTENSET = NRF_SPIM_INT_END_MASK | NRF_SPIM_INT_STOPPED_MASK;

        OLED_SPIXM->TXD.LIST = 1;
        OLED_SPIXM->RXD.LIST = 0;

        OLED_SPIXM->RXD.PTR = (uint32_t)&spi_rx_buffer; // READ BUFF un use
        OLED_SPIXM->RXD.MAXCNT = 1;

        /* Enable */
        nrf_spim_enable(OLED_SPIXM);
        // OLED_SPIXM->ENABLE = (SPIM_ENABLE_ENABLE_Enabled << SPIM_ENABLE_ENABLE_Pos);

        nrf_drv_common_irq_enable(SPI1_TWI1_IRQn, APP_IRQ_PRIORITY_MID);

        // SPI  参数配置
        //以下配置和SPIM共享，不需要重复配置
        //        OLED_SPIX->PSELSCK  = OLED_CLOCK_PIN;
        //        OLED_SPIX->PSELMOSI = OLED_DATA_PIN;
        //        OLED_SPIX->FREQUENCY = (uint32_t) OLED_SPI_FREQ;
        //        config_mode = (SPI_CONFIG_CPHA_Trailing << SPI_CONFIG_CPHA_Pos) | (SPI_CONFIG_CPOL_ActiveLow << SPI_CONFIG_CPOL_Pos);
        //        OLED_SPIX->CONFIG = (config_mode | (SPI_CONFIG_ORDER_MsbFirst << SPI_CONFIG_ORDER_Pos));

        OLED_SPIX->EVENTS_READY = 0U;
        /* Enable */
        OLED_SPIX->ENABLE = (SPI_ENABLE_ENABLE_Enabled << SPI_ENABLE_ENABLE_Pos);
    }

    spi_ctrl.config_mode = spi_cfg_mode;
}

/*********************************************************************************************
 * @brief SPI配置获取
 * @warning
 ********************************************************************************************/
uint8_t oled_spi_get_mode(void)
{
    return spi_ctrl.config_mode;
}

/*********************************************************************************************
 * @brief 等待SPI buffer有空
 * @param [in] param1
 * @warning
 ********************************************************************************************/
static inline void spi_fw_wait_buffer_notfull(void)
{
    if (spi_ctrl.spi_data_in_buffer >= SPI_HW_BUFFER_COUNT)
    {
        uint32_t count = 0;
        while ((OLED_SPIX->EVENTS_READY == 0U) && (count++ < TIMEOUT_COUNTER_10MS))
            ;
        OLED_SPIX->EVENTS_READY = 0U;
        uint8_t dummy = (uint8_t)OLED_SPIX->RXD;
        spi_ctrl.spi_data_in_buffer--;
    }
}

/*********************************************************************************************
 * @brief 发送多个数据，注意使用oled_spi_hw_wait_end()等待结束
 * @param [in] param1
 * @warning
 ********************************************************************************************/
void oled_spi_tx_bytes_hw(const uint8_t *p_data, uint16_t length)
{

    while (length--)
    {
        spi_fw_wait_buffer_notfull();
        OLED_SPIX->TXD = *p_data++;

        spi_ctrl.spi_data_in_buffer++;
    }
}

/*********************************************************************************************
 * @brief 等待SPI缓冲区内容发送完成
 * @warning
 ********************************************************************************************/
void oled_spi_hw_wait_end(void)
{
    while (spi_ctrl.spi_data_in_buffer)
    {
        uint32_t temp32 = 0;
        while ((OLED_SPIX->EVENTS_READY == 0U) && (temp32++ < TIMEOUT_COUNTER_10MS))
        {
        }
        OLED_SPIX->EVENTS_READY = 0U;
        uint8_t dummy = (uint8_t)OLED_SPIX->RXD;
        spi_ctrl.spi_data_in_buffer--;
    }
}

/*********************************************************************************************
 * @brief oled写指令
 * @param [in] param1
 * @warning
 ********************************************************************************************/
void oled_write_cmd(uint8_t cmd)
{
    // #warning debug
    if (get_spi_mode() != SPI_CFG_MODE_SOFTWARE)
    {
        spi_switch_mode(SPI_CFG_MODE_HARDWARE);
    }
    nrf_gpio_pin_clear(OLED_CS_PIN);
    nrf_gpio_pin_clear(OLED_DC_PIN);
    oled_spi_tx_byte(cmd);
    nrf_gpio_pin_set(OLED_CS_PIN);
}
/*********************************************************************************************
 * @brief oled写数据
 * @param [in] param1
 * @warning
 ********************************************************************************************/
void oled_write_data(uint8_t data)
{
    nrf_gpio_pin_clear(OLED_CS_PIN);
    nrf_gpio_pin_set(OLED_DC_PIN);
    oled_spi_tx_byte(data);
    nrf_gpio_pin_set(OLED_CS_PIN);
}

/*********************************************************************************************
 * @brief oled写数据
 * @param [in] param1
 * @warning
 ********************************************************************************************/
void oled_write_data_bytes(uint8_t *p_data, uint16_t len)
{
    nrf_gpio_pin_clear(OLED_CS_PIN);
    nrf_gpio_pin_set(OLED_DC_PIN);
    if (spi_ctrl.config_mode == SPI_CFG_MODE_SOFTWARE)
    {
        while (len--)
        {
            oled_spi_tx_byte(*p_data++);
        }
    }
    else
    {
        oled_spi_tx_bytes_hw(p_data, len);
        oled_spi_hw_wait_end();
    }
    nrf_gpio_pin_set(OLED_CS_PIN);
}
